package gamedemo.component.movement;

import gamedemo.component.Component;
import gamedemo.geom.Vector3f;

import org.newdawn.slick.GameContainer;
import org.newdawn.slick.state.StateBasedGame;

/**
 * @author thi
 *
 */
public abstract class MovementComponent extends Component {
	protected static final float GROUND = 500; //Temporary thingy
	protected static final float JUMP_POWER = 1.1f;
	protected static final float GRAVITY = 0.003f;

	protected static float MAX_VELOCITY = 0.5f;
	protected static final float GROUND_FRICTION = 1.01f;
	protected static final float AIR_FRICTION = 1.002f;
	
	protected float groundAcceleration = calculateAcceleration(GROUND_FRICTION, MAX_VELOCITY);
	protected float airAcceleration = calculateAcceleration(AIR_FRICTION, MAX_VELOCITY);
	
	protected Vector3f velocity = new Vector3f();
	protected Vector3f direction = new Vector3f(0, 0, 0);
	
	public Vector3f getVelocity() {
		return velocity;
	}

	public Vector3f getDirection() {
		return direction;
	}

	protected float maxVelocity = maxVelocity(groundAcceleration, GROUND_FRICTION);
	
	public boolean groundBound = false;

	protected void moveLeft(int delta) {
		direction.x = -1;
	}

	protected void moveRight(int delta) {
		direction.x = 1;
	}

	protected void moveUp(int delta) {
		direction.z = 1;
	}

	protected void moveDown(int delta) {
		direction.z = -1;
	}

	protected void jump(int delta) {
		if (groundBound) {
			groundBound = false;
			velocity.y = -JUMP_POWER;
		}
	}

	
	/**
	 * NEWVELOCITY():
	 * v(0) = v
	 * v(1) = v(0) / F + a =
	 *      = v / F + a.
	 * v(2) = v(1) / F + a =
	 *      = (v / F + a) / F + a = 
	 *      = v / F^2 + a / F + a.
	 * v(3) = v / F^3 + a / F^2 + a / F + a.
	 * ....
	 * v(delta) = v / F^(delta) + a * sum(1/F^(k)), k = 0...delta-1.
	 * 
	 * -> geometrisk summa -> profit (snabbare kod)
	 * v(delta) = v / F^(delta) + a * (((1 / F^(delta)) - 1) / ((1 / F) - 1)), where F != 1.
	 * v(delta) = v / F^(delta) + a * delta, where F == 1.
	 */
	protected float newVelocity(float velocity, float acceleration, float friction, int delta) {
		float newVelocity = (float) (velocity / Math.pow(friction, delta));
		if (friction == 1) {
			return newVelocity + acceleration * delta;
		} else {
			return (float) (newVelocity + acceleration * ((1 / Math.pow(friction, delta)) - 1) / ((1 / friction) - 1));
		}
	}
	
	/**
	 * To get the terminal velocity for given acceleration and friction the formula for newVelocity() is used.
	 * Calculate v(infinity) to get the terminal velocity.
	 * 
	 * The function can be written:
	 * v(infinity) ~ -a / ((1 / F) - 1)
	 */
	protected float maxVelocity(float acceleration, float friction) {
		return -acceleration / ((1 / friction) - 1);
	}
	
	/**
	 * The terminal velocity is given by
	 * v(infinity) ~ -a / ((1 / F) - 1)  (see maxVelocity()).
	 * 
	 * This function can be rewritten to get the acceleration needed to obtain a given terminal velocity.
	 * a = -v(infinity) / F + v(infinity), 
	 * where v(infinity) is the terminal velocity.
	 */
	protected float calculateAcceleration(float friction, float maxVelocity) {
		return -maxVelocity / friction + maxVelocity;
	}

	protected float applyGravity(float velocity, float gravity, int delta) {
		return velocity + gravity * delta;
	}
	
	//Namnet suger, deal with it
	protected void updateStuff(float friction, float gravity, int delta) {
		Vector3f oldVelocity = new Vector3f(velocity);
		
		if (groundBound) {
			velocity.x = newVelocity(velocity.x, direction.x * groundAcceleration, GROUND_FRICTION, delta);
			velocity.z = newVelocity(velocity.z, direction.z * groundAcceleration, GROUND_FRICTION, delta);
			velocity.y = 0;
		} else {
			velocity.x = newVelocity(velocity.x, direction.x * airAcceleration, AIR_FRICTION, delta);
			velocity.z = newVelocity(velocity.z, direction.z * airAcceleration, AIR_FRICTION, delta);
			velocity.y = applyGravity(velocity.y, gravity, delta);
		}
		
		Vector3f position = owner.getPosition();

		position.x = position.x + delta * velocity.x;
		//position.y += delta * velocity.y;
		position.y = position.y + (float)delta / 2 * (oldVelocity.y + velocity.y); //Heun's method
		position.z = position.z + delta * velocity.z;
		

		if (position.y + owner.getHeight() >= GROUND) {
			groundBound = true;
			position.y = GROUND - owner.getHeight();
		}
	}

	/**
	 * Should always be called first in the update method. Movement will still be functional if called last, but then
	 * other components/entities won't be able to see the owner entity's acceleration.
	 */
	protected void resetAcceleration() {
		direction.z = 0;
		direction.x = 0;
	}

	@Override
	public abstract void update(GameContainer gc, StateBasedGame sb, int delta);
}
